package com.ray.woodencreate.interceptor;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ray.system.service.compose.RedisService;
import com.ray.woodencreate.annotation.Login;
import com.ray.woodencreate.annotation.NoAuth;
import com.ray.woodencreate.annotation.NoLogin;
import com.ray.woodencreate.annotation.RequestAuth;
import com.ray.woodencreate.beans.LoginUser;
import com.ray.woodencreate.jwt.Jwt;
import com.ray.woodencreate.logs.SystemLogBuilder;
import com.ray.woodencreate.result.MsgCodeConstant;
import com.ray.woodencreate.result.ResultFactory;
import com.ray.woodencreate.util.LogInUserUtil;
import com.ray.woodencreate.util.TokenInfoUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;

/**
 * @author bo shen
 * @Description: 权限拦截器
 * @Class: AuthInterceptor
 * @Package com.toptoday.woodencreate.interceptor
 * @date 2018/10/24 9:44
 * @company <p>Ray快速开发平台</p>
 * @updateRecord time(修改时间)  author(修改人)   desc(修改内容)
 */
@Component
@Slf4j
public class AuthInterceptor implements HandlerInterceptor, ApplicationContextAware {
    /**
     * 权限
     */
    public final static String AUTHORIZATION = "sessionKey";


    private ApplicationContext applicationContext;

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (log.isDebugEnabled()) {
            log.info(new SystemLogBuilder().appendLevelTips().appendMsgCode(MsgCodeConstant.Template.TEMP00000001).bulidString());
        }
        String url = request.getRequestURI();
        log.info(new SystemLogBuilder().appendLevelTips().appendMsgCode(MsgCodeConstant.Template.TEMP00000005, url).bulidString());

        //解析token
        String token = getToken(request);
        if (!StringUtils.isEmpty(token)) {
            addUser(token, response);
        } else if(!notNeedLogin(handler)) {
            //没有登陆
            sendError(response, MsgCodeConstant.Error.ERR88000002, 802);
        }
        //权限判断
        if (!notNeedPermission(handler) && !notRestRequest(handler)) {
            //判断是否具有操作权限
            if (!checkOpAuth(handler, url, token)) {
                //无权限直接返回
                sendError(response, MsgCodeConstant.Error.ERR88000001, 801);
            }
        }
        log.info(new SystemLogBuilder().appendLevelTips().appendMsgCode(MsgCodeConstant.Template.TEMP00000002, url).bulidString());
        return true;
    }


    private void addUser(String token, HttpServletResponse response) throws Exception {
        //token校验是否存在
        if (!existToken(token)) {
            //无权限直接返回
            sendError(response, MsgCodeConstant.Error.ERR88000002, 802);
        }
        log.info(new SystemLogBuilder().appendLevelTips().appendMsgCode(MsgCodeConstant.Template.TEMP00000004, token).bulidString());
        //解析用户信息
        RedisService redisService = applicationContext.getBean(RedisService.class);
        if (ObjectUtil.isNotNull(redisService)) {
            String userInfo = redisService.getValue(token);
            if (StrUtil.isNotBlank(userInfo)) {
                LoginUser user = JSON.parseObject(userInfo, LoginUser.class);
                if (ObjectUtils.isEmpty(user)) {
                    //无权限直接返回
                    sendError(response, MsgCodeConstant.Error.ERR88000002, 802);
                }
                //用户放入当前线程
                LogInUserUtil.add(user);
            }
        } else {
            //无权限直接返回
            sendError(response, MsgCodeConstant.Error.ERR88000002, 802);
        }
    }


    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
        //登录方法
        if (isLogin(handler)) {
            log.info(new SystemLogBuilder().appendLevelTips().appendMsgCode(MsgCodeConstant.Template.TEMP00000004, TokenInfoUtil.get()).bulidString());
            response.setHeader(AUTHORIZATION, TokenInfoUtil.get());
            Cookie cookie = new Cookie(AUTHORIZATION, TokenInfoUtil.get());
            response.addCookie(cookie);
        }
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {

    }

    /**
     * 是否需要权限
     *
     * @param handler
     * @return
     */
    private boolean notNeedPermission(Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法上的不需要权限的注解
            NoAuth noAuth = handlerMethod.getMethod().getAnnotation(NoAuth.class);
            // 如果方法上的注解为空 则获取类的注解
            if (noAuth == null) {
                return false;
            }
        }
        return true;
    }

    /**
     * 是否需要权限
     *
     * @param handler
     * @return
     */
    private boolean notNeedLogin(Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法上的不需要权限的注解
            NoLogin noLogin = handlerMethod.getMethod().getAnnotation(NoLogin.class);
            // 如果方法上的注解为空 则获取类的注解
            if (noLogin == null) {
                return false;
            }
        }
        return true;
    }


    /**
     * 不是rest请求
     *
     * @param handler
     * @return
     */
    private boolean notRestRequest(Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            ResponseBody method = handlerMethod.getMethod().getAnnotation(ResponseBody.class);
            ResponseBody clazz = handlerMethod.getMethod().getDeclaringClass().getAnnotation(ResponseBody.class);
            RestController restController = handlerMethod.getMethod().getDeclaringClass().getAnnotation(RestController.class);
            if (method == null && clazz == null && restController == null) {
                return true;
            }
        }
        return false;
    }

    /**
     * 设置权限
     *
     * @param handler
     * @return
     */
    private boolean checkOpAuth(Object handler, String authPath, String token) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            RequestAuth method = handlerMethod.getMethod().getAnnotation(RequestAuth.class);
            //不用拦截
            if (method == null) {
                return true;
            }
            if (StringUtils.isEmpty(authPath)) {
                return false;
            }
            Set<String> opAuths = loadOpAuths(token);
            return opAuths.contains(authPath);
        }
        return true;
    }

    private Set<String> loadOpAuths(String token) {
        //先从缓存中获取
        Set<String> list = null;
        RedisService redisService = applicationContext.getBean(RedisService.class);
        if (ObjectUtil.isNotNull(redisService)) {
            String authString = redisService.getValue(token);
            if (StrUtil.isNotBlank(authString)) {
                list = JSON.parseObject(authString, Set.class);
            }
        }
        return new HashSet<>();
    }

    /**
     * 是否是白名单路径
     *
     * @return
     */
    private boolean IsWhitePath() {

        return true;
    }

    /**
     * 是否存在token
     *
     * @param token
     * @return
     */
    private boolean existToken(String token) {
        //放入redis
        StringRedisTemplate stringRedisTemplate = applicationContext.getBean(StringRedisTemplate.class);
        if (!ObjectUtils.isEmpty(stringRedisTemplate)) {
            return StringUtils.hasLength(stringRedisTemplate.opsForValue().get(token));
        }
        return false;
    }


    /**
     * 是否是登录方法
     *
     * @param handler
     * @return
     */
    private boolean isLogin(Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            // 获取方法上的登录注释
            Login login = handlerMethod.getMethod().getAnnotation(Login.class);
            // 如果方法上的注解为空 则获取类的注解
            if (login == null) {
                return false;
            }
        }
        return true;
    }

    /**
     * 发送错误信息
     */
    private void sendError(HttpServletResponse response, String errorCode, int status) throws Exception {
        OutputStream outputStream = null;
        try {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
            response.setStatus(status);
            outputStream = response.getOutputStream();
            String json = new ObjectMapper().writeValueAsString(ResultFactory.createErrorResult(errorCode));
            outputStream.write(json.getBytes());
            outputStream.flush();
            outputStream.close();
        } finally {
            if (outputStream != null) {
                try {
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }


    String getToken(HttpServletRequest request) {
        String token = request.getParameter(AUTHORIZATION);
        //解析tokeno
        if (StringUtils.isEmpty(token)) {
            token = request.getHeader(AUTHORIZATION);
        }
        //从cookie中获取
        if (StringUtils.isEmpty(token)) {
            Cookie[] cookies = request.getCookies();
            if (cookies != null) {
                for (Cookie cookie : cookies) {
                    if (StringUtils.pathEquals(cookie.getName(), AUTHORIZATION)) {
                        token = cookie.getValue();
                    }
                }
            }
            log.info(new SystemLogBuilder().appendLevelTips().appendMsgCode(MsgCodeConstant.Template.TEMP00000004, token).bulidString());
        }
        return token;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
